/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on May 21, 2004
*
*/
package com.python.pydev.refactoring.actions;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.ui.actions.OpenAction;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.progress.UIJob;
import org.python.pydev.core.IToken;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.parser.IParserObserver;
import org.python.pydev.core.parser.ISimpleNode;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.actions.PyOpenAction;
import org.python.pydev.editor.actions.refactoring.PyRefactorAction;
import org.python.pydev.editor.codecompletion.PyCodeCompletionImages;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.editor.codecompletion.revisited.javaintegration.AbstractJavaClassModule;
import org.python.pydev.editor.codecompletion.revisited.javaintegration.JavaDefinition;
import org.python.pydev.editor.model.ItemPointer;
import org.python.pydev.editor.refactoring.AbstractPyRefactoring;
import org.python.pydev.editor.refactoring.IPyRefactoring;
import org.python.pydev.editor.refactoring.RefactoringRequest;
import org.python.pydev.editor.refactoring.TooManyMatchesException;
import org.python.pydev.parser.PyParser;
import org.python.pydev.plugin.PydevPlugin;
/**
* This is a refactoring action, but it does not follow the default cycle -- so, it overrides the run
* and always uses the same cycle... because in this case, we do not need any additional information
* before starting the refactoring... the go to definition always only depends upon the current
* selected text -- and if more than 1 match is found, it asks the user to select the one that
* is more likely the match)
*
* @author Fabio Zadrozny
*/
public class PyGoToDefinition extends PyRefactorAction {
/**
* We do some additional checking because the default backend
* @return true if the conditions are ok and false otherwise
*/
protected boolean areRefactorPreconditionsOK(RefactoringRequest request) {
// we're working with dirty editors now (through pushTemporaryModule/popTemporaryModule)
//
// if (request.pyEdit.isDirty()){
// request.pyEdit.doSave(null);
// }
return true;
}
/**
* This class makes the parse when all reparses have finished.
*/
private class FindParserObserver implements IParserObserver {
/**
* Lock for accessing askReparse.
*/
private Object lock;
/**
* A set with all the reparses asked. When all finish, we'll do the find.
*/
private Set<PyEdit> askReparse;
/**
* This is the editor which this action is listening in the reparse (will remove it from
* askReparse and when empty, will proceed to do the find).
*/
private PyEdit editToReparse;
public FindParserObserver(PyEdit editToReparse, Set<PyEdit> askReparse, Object lock) {
this.editToReparse = editToReparse;
this.askReparse = askReparse;
this.lock = lock;
}
/**
* As soon as the reparse is done, this method is called.
*/
public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc) {
editToReparse.getParser().removeParseListener(this); //we'll only listen for this single parse
doFindIfLast();
}
/**
* We want to work in the event of parse errors too.
*/
public void parserError(Throwable error, IAdaptable file, IDocument doc) {
editToReparse.getParser().removeParseListener(this); //we'll only listen for this single parse
doFindIfLast();
}
/**
* Remove the editor from askReparse and if it's the last one, do the find.
*/
private void doFindIfLast() {
synchronized (lock) {
askReparse.remove(editToReparse);
if (askReparse.size() > 0) {
return; //not the last one (we'll only do the find when all are reparsed.
}
}
/**
* Create an ui job to actually make the find.
*/
UIJob job = new UIJob("Find") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
try {
findDefinitionsAndOpen(true);
} catch (Throwable e) {
Log.log(e);
}
return Status.OK_STATUS;
}
};
job.setPriority(Job.INTERACTIVE);
job.schedule();
}
}
/**
* Overrides the run and calls -- and the whole default refactoring cycle from the beggining,
* because unlike most refactoring operations, this one can work with dirty editors.
* @return
*/
public void run(IAction action) {
workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IEditorPart[] dirtyEditors = workbenchWindow.getActivePage().getDirtyEditors();
Set<PyEdit> askReparse = new HashSet<PyEdit>();
for (IEditorPart iEditorPart : dirtyEditors) {
if (iEditorPart instanceof PyEdit) {
PyEdit pyEdit = (PyEdit) iEditorPart;
long astModificationTimeStamp = pyEdit.getAstModificationTimeStamp();
IDocument doc = pyEdit.getDocument();
if (astModificationTimeStamp != -1
&& astModificationTimeStamp == (((IDocumentExtension4) doc).getModificationStamp())) {
//All OK, the ast is synched!
} else {
askReparse.add(pyEdit);
}
}
}
if (askReparse.size() == 0) {
findDefinitionsAndOpen(true);
} else {
//We don't have a match: ask for a reparse
Object lock = new Object();
for (PyEdit pyEdit : askReparse) {
IParserObserver observer = new FindParserObserver(pyEdit, askReparse, lock);
PyParser parser = pyEdit.getParser();
parser.addParseListener(observer); //it will analyze when the next parse is finished
parser.forceReparse();
}
}
}
public ItemPointer[] findDefinitionsAndOpen(boolean doOpenDefinition) {
request = null;
ps = new PySelection(getTextEditor());
final PyEdit pyEdit = getPyEdit();
RefactoringRequest refactoringRequest;
try {
refactoringRequest = getRefactoringRequest();
} catch (MisconfigurationException e1) {
Log.log(e1);
return new ItemPointer[0];
}
final Shell shell = getShell();
try {
if (areRefactorPreconditionsOK(refactoringRequest)) {
ItemPointer[] defs = findDefinition(pyEdit);
if (doOpenDefinition) {
openDefinition(defs, pyEdit, shell);
}
return defs;
}
} catch (Exception e) {
Log.log(e);
String msg = e.getMessage();
if (msg == null) {
msg = "Unable to get error msg (null)";
}
ErrorDialog.openError(shell, "Error", "Unable to do requested action",
new Status(Status.ERROR, PydevPlugin.getPluginID(), 0, msg, e));
}
return null;
}
/**
* Opens a given definition directly or asks the user to choose one of the passed definitions
*
* @param defs item pointers with the definitions available for opening.
* @param pyEdit pyedit where the action to open the definition was started
* @param shell the shell to be used to show dialogs
*/
public static void openDefinition(ItemPointer[] defs, final PyEdit pyEdit, final Shell shell) {
if (defs == null) {
shell.getDisplay().beep();
return;
}
HashSet<ItemPointer> set = new HashSet<ItemPointer>();
for (ItemPointer pointer : defs) {
if (pointer.file != null) {
set.add(pointer);
}
}
final ItemPointer[] where = set.toArray(new ItemPointer[0]);
if (where == null) {
shell.getDisplay().beep();
return;
}
if (where.length > 0) {
if (where.length == 1) {
ItemPointer itemPointer = where[0];
doOpen(itemPointer, pyEdit, shell);
} else {
//the user has to choose which is the correct definition...
final Display disp = shell.getDisplay();
disp.syncExec(new Runnable() {
public void run() {
ElementListSelectionDialog dialog = new ElementListSelectionDialog(shell, new ILabelProvider() {
public Image getImage(Object element) {
return PyCodeCompletionImages.getImageForType(IToken.TYPE_PACKAGE);
}
public String getText(Object element) {
ItemPointer pointer = (ItemPointer) element;
File f = (File) (pointer).file;
int line = pointer.start.line;
return f.getName() + " (" + f.getParent() + ") - line:" + line;
}
public void addListener(ILabelProviderListener listener) {
}
public void dispose() {
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void removeListener(ILabelProviderListener listener) {
}
});
dialog.setTitle("Found matches");
dialog.setTitle("Select the one you believe matches most your search.");
dialog.setElements(where);
dialog.open();
Object[] result = dialog.getResult();
if (result != null && result.length > 0) {
doOpen((ItemPointer) result[0], pyEdit, shell);
}
}
});
}
} else {
shell.getDisplay().beep();
}
}
/**
* @param itemPointer this is the item pointer that gives the location that should be opened
* @param pyEdit the editor (so that we can gen the open action)
* @param shell
*/
private static void doOpen(ItemPointer itemPointer, PyEdit pyEdit, Shell shell) {
File f = (File) itemPointer.file;
String filename = f.getName();
if (PythonPathHelper.isValidSourceFile(filename) || filename.indexOf('.') == -1 || //treating files without any extension!
(itemPointer.zipFilePath != null && PythonPathHelper.isValidSourceFile(itemPointer.zipFilePath))) {
final PyOpenAction openAction = (PyOpenAction) pyEdit.getAction(PyEdit.ACTION_OPEN);
openAction.run(itemPointer);
} else if (itemPointer.definition instanceof JavaDefinition) {
//note that it will only be able to find a java definition if JDT is actually available
//so, we don't have to care about JDTNotAvailableExceptions here.
JavaDefinition javaDefinition = (JavaDefinition) itemPointer.definition;
OpenAction openAction = new OpenAction(pyEdit.getSite());
StructuredSelection selection = new StructuredSelection(new Object[] { javaDefinition.javaElement });
openAction.run(selection);
} else {
String message;
if (itemPointer.definition != null && itemPointer.definition.module instanceof AbstractJavaClassModule) {
AbstractJavaClassModule module = (AbstractJavaClassModule) itemPointer.definition.module;
message = "The definition was found at: " + f.toString() + "\n" + "as the java module: "
+ module.getName();
} else {
message = "The definition was found at: " + f.toString() + "\n"
+ "(which cannot be opened because it is a compiled extension)";
}
MessageDialog.openInformation(shell, "Compiled Extension file", message);
}
}
/**
* @return an array of ItemPointer with the definitions found
* @throws MisconfigurationException
* @throws TooManyMatchesException
*/
public ItemPointer[] findDefinition(PyEdit pyEdit) throws TooManyMatchesException, MisconfigurationException {
IPyRefactoring pyRefactoring = AbstractPyRefactoring.getPyRefactoring();
return pyRefactoring.findDefinition(getRefactoringRequest());
}
/**
* As we're not using the default refactoring cycle, this method is not even called
*/
protected String perform(IAction action, IProgressMonitor monitor) throws Exception {
return null;
}
}